Spring Security | Note-17

Spring Security Note-17


Spring Security授权简介

Spring Security授权定义

我们所谓的菜单,按钮触发的对应的一个后台的URL,授权并不是看见与否,而是能否访问;

安全问题,涉及的是访问与否的问题;

在用户体验的情况下,会给出所谓的按钮和菜单,但是在没有权限访问的前提下,最终依然没有访问的能力,给出的是提示;

涉及的用户体验和安全,要求不同,给出的解决方案是不同,最终我们只需要将自定义开发权限模块和Spring Security对接即可;

举例

业务系统(普通用户)、内管系统(公司运营人员),对于这两个系统来说,授权是不同的;

在业务系统当中,只区分是否登录(浏览、操作)或区分简单的角色(普通、VIP),权限规则基本不变;

在内管系统当中,角色众多,权限复杂,权限规则随着体系和业务的发展不断变化;

在应用系统内部,需要两份数据,一份是系统配置信息(每个URL需要的权限),一份是用户权限信息(用户1有ABC权限),对于请求,将会对两份数据进行比对以获取访问请求;

应用

在BrowserSecurityConfig的authorizeRequests()已经进行URL的配置;

接下去对于角色的验证

1
2
3
4
// 指定URL的角色
.antMatchers("/user").hasRole("ADMIN")
// 指定请求方法
.antMatchers(HttpMethod.GET,"/user/*").hasRole("ADMIN")

MyUserDetailService,可以指定返回用户的权限

1
2
3
4
5
6
7
8
9
private SocialUserDetails buildUser(String username) {
// TODO 根据用户名(数据库)查找用户信息
// 根据查找到的用户信息,判断用户是否被冻结
String password = passwordEncoder.encode("123456");
logger.info("PASSWORD FROM DB : " + password);
return new SocialUser(username, password,
true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER,ROLE_ADMIN"));
}

Spring Security源码解析

在这里的学习中,我们重点关注橙色的部分Filter Security Interceptor,它最终决定你的请求是否能够通过,访问到REST API ;

如果不能访问,它会抛出异常,并且说明原因,由蓝色部分Exception Translation Filter来处理;

Anonymous Authentication Filter匿名认证过滤器,位于绿色过滤器链段最后一端;

Anonymous Authentication Filter

这个类十分简单,它主要关注你当前的SecurityContextHolder是否有Authentication,判断是否为空,实际上就是在判断,前面绿色过滤器链是否完成身份认证,如果为空,那么就会创建一个Authentication

这个由匿名认证过滤器创建的Authentication,是匿名的principal

通过Anonymous Authentication Filter,最终都会带有你的身份,去到Filter Security Interceptor,由它来判断你的身份是否有对API的访问权限;

1536113901081

上面此图,是与Spring Security授权相关的类和接口;

最核心的三个类FilterSecurityInterceptor & AccessDecisionManager & AccessDecisionVoter

Filter Security Interceptor

整个控制授权的主入口;

Access Decision Manager

访问决定的管理者,它由一个抽象类Abstract Access Decision Manager,和三个具体实现AffirmativeBased & ConsensusBased & UnanimouosBased

它在抽象类中,管理一组AccessDecisionVoter

Abstract Access Decision Manager则会综合所有投票者的决定,给出一个最终的结果;

最终的结果,是有三套具体实现的逻辑;

AffirmativeBased 只要有一个通过就通过;

ConsensusBased 多数通过逻辑;

UnanimouosBased只要有一个不通过就不通过;

默认Spring Security使用的是第一个逻辑,只要有一个通过就通过的逻辑;

Access Decision Voter

Voter是投票者,有一组投票者,每个投票者有不同的处理逻辑;

判断Authentication是否有某种角色,是否经过完全的身份认证;

每个投票者根据自己的逻辑,投出自己的一票,投出过还是不过;

Security Config

判断你的授权是否通过,需要两块数据的配置信息;

Security Config会将系统配置信息读取出来,封装成一组Config Attribute的对象;

这一组对象中,每一个Config Attribute都会带有每一个URL所需要的权限;

Security Context Holder

当前用户的权限信息,会封装在Authentication当中;

-

在进入到Access Decision Manager之前,会封装三部分的信息Config AttributeAuthentication请求信息,成一个对象,进行授权认证的判断;

在Spring Security3.x以后,新产生的Wen Expression Voter包办了所有的Voter的工作,在WEB的环境下,所有的Voter的工作由它承担;


权限表达式

Spring Security允许我们在定义URL访问或方法访问所应有的权限时使用Spring EL表达式,在定义所需的访问权限时如果对应的表达式返回结果为true则表示拥有对应的权限,反之则无;

表达式 描述
hasRole([role]) 当前用户是否拥有指定角色。
hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
hasAuthority([auth]) 等同于hasRole
hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole
Principle 代表当前用户的principle对象
authentication 直接从SecurityContext获取的当前Authentication对象
permitAll 总是返回true,表示允许所有的
denyAll 总是返回false,表示拒绝所有的
isAnonymous() 当前用户是否是一个匿名用户
isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的
isAuthenticated() 表示当前用户是否已经登录认证成功了。
isFullyAuthenticated() 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。

-

多权限表达式

img

-

实现

配置提供者
1
2
3
public interface AuthorizeConfigProvider {
void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}
具体实现配置提供者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class ImoocAuthorizeConfigProvider implements AuthorizeConfigProvider{
@Autowired
private SecurityProperties securityProperties;
@Override
public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
config.antMatchers(
SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
securityProperties.getBrowser().getLoginPage(),
securityProperties.getBrowser().getSignUpUrl(),
securityProperties.getBrowser().getSession().getSessionInvalidUrl(),
securityProperties.getBrowser().getSignOutUrl()
).permitAll();
}
}
配置管理器
1
2
3
public interface AuthorizeConfigManager {
void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}
具体实现配置管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class ImoocAuthorizeConfigManager implements AuthorizeConfigManager {
@Autowired
private Set<AuthorizeConfigProvider> authorizeConfigProviders;

@Override
public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
for (AuthorizeConfigProvider authorizeConfigProvider : authorizeConfigProviders) {
authorizeConfigProvider.config(config);
}
config.anyRequest().authenticated();
}
}
修改浏览器安全配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class BrowserSecurityConfig extends AbstractChannelSecurityConfig {
@Autowired
private AuthorizeConfigManager authorizeConfigManager;

@Override
protected void configure(HttpSecurity http) throws Exception {
applyPasswordAuthenticationConfig(http);
http.apply(validateCodeSecurityConfig)
...
// 删除Cookies
.deleteCookies("JSESSIONID")
.and()
.csrf().disable();
authorizeConfigManager.config(http.authorizeRequests());
}
}
修改APP资源管理器安全配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@EnableResourceServer
public class ImoocResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private AuthorizeConfigManager authorizeConfigManager;
@Override
public void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
.loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
.successHandler(imoocAuthenticationSuccessHandler)
.failureHandler(imoocAuthenticationFailureHandler);
http
.apply(validateCodeSecurityConfig)
.and()
.apply(smsCodeAuthenticationSecurityConfig)
.and()
.apply(imoocSocialSecurityConfig)
.and()
.apply(openIdAuthenticationSecurityConfig)
.and().csrf().disable();
authorizeConfigManager.config(http.authorizeRequests());
}
}

基于数据库RBAC数据模型控制权限

在内管系统中,以满足角色众多,权限复杂,权限规则随着业务的发展不断变化的问题,我们需要将权限放入到数据库当中,以添加,修改,删除数据的方式满足需要;

通用RBAC数据模型

RBAC(Role-Based Access Control):有大约5张表组成,3张实体表,2张关系表,分别是:

用户表:存储用户信息,由业务人员维护;

角色表:存储角色信息,由业务人员维护;

资源表:存储资源信息,菜单,按钮及其URL等,由开发人员维护;

用户-角色关系表(n-n):存储用户和角色的对应关系,由业务人员维护;

角色-资源关系表(n-n):存储角色和资源的对应关系,由业务人员维护;

声明权限服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class RbacServiceImpl implements RbacService {
private AntPathMatcher antPathMatcher = new AntPathMatcher();

@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
boolean hasPermission = false;
if (principal instanceof UserDetails) {
String username = ((UserDetails) principal).getUsername();
// 读取用户所拥有权限的所有URL
Set<String> urls = new HashSet<>();
for (String url : urls) {
if (antPathMatcher.match(url, request.getRequestURI())) {
hasPermission = true;
break;
}
}
}
return hasPermission;
}
}
1
2
3
4
5
6
7
8
@Component
@Order(Integer.MAX_VALUE)
public class DemoAuthorizeConfigProvider implements AuthorizeConfigProvider {
@Override
public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
config.anyRequest().access("@rbacService.hasPermission(request,authentication)");
}
}